#region Usings

using System;
using System.Collections;
using System.Collections.Generic;

using Gonte.Xml;
using Gonte.Xml.Serialization;
	
#endregion

namespace Gonte.Collections
{
    /// <summary>
    /// Implements a dictionary with a case insensitive string key
    /// </summary>
    /// <typeparam name="T">The field of the item</typeparam>
    public class StringKeyDictionary<T>
        : IDictionary
        , IXmlSerializableObject
    {
        #region Delegates

        /// <summary>
        /// Determines where the item matches the object being searched
        /// </summary>
        /// <param name="item">An existing item in the dictionary</param>
        /// <param name="@object">The object to match</param>
        /// <returns>True if @object matches the item, otherwise returns false</returns>
        public delegate bool IsMatch(T item, Object obj); 

        #endregion

        #region Constructors

        public StringKeyDictionary()
        {
            dictionary_ = new Dictionary<string, T>();
        }

        public StringKeyDictionary(int capacity)
        {
            dictionary_ = new Dictionary<string, T>(capacity);
        } 

        #endregion

        #region Properties

        public int Count
        {
            get
            {
                return dictionary_.Count;
            }
        }

        public Dictionary<string, T>.KeyCollection Keys
        {
            get
            {
                return dictionary_.Keys;
            }
        }

        public Dictionary<string, T>.ValueCollection Values
        {
            get
            {
                return dictionary_.Values;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Tests whether the dictionary contains the given key
        /// </summary>
        /// <param name="key">The key to the item</param>
        /// <returns>True if the key is in the dictionary, else returns false</returns>
        public bool ContainsKey(string key)
        {
            string caseInsensitiveKey = key.Trim().ToLower();
            return dictionary_.ContainsKey(caseInsensitiveKey);
        }

        /// <summary>
        /// Adds a key to the dictionary
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        public void Add(string key, T value)
        {
            string caseInsensitiveKey = key.Trim().ToLower();
            if (dictionary_.ContainsKey(caseInsensitiveKey))
            {
                if (repeatedItem_ != null)
                {
                    repeatedItem_(this, new RepeatedItemEventArgs(key, value));
                    return; // Do not add it
                }

                throw new ArgumentException("Key already exists in the dictionary", key);
            }

            dictionary_.Add(caseInsensitiveKey, value);

            if (afterInsert_ != null)
            {
                afterInsert_(this, new AfterInsertEventArgs(key, value));
            }
        }

        /// <summary>
        /// Indexer to access an item of the collection
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public T this[string key]
        {
            get
            {
                string caseInsensitiveKey = key.Trim().ToLower();
                if (!dictionary_.ContainsKey(caseInsensitiveKey))
                {
                    if (itemNotFound_ != null)
                    {
                        itemNotFound_(this, new ItemNotFoundEventArgs(caseInsensitiveKey));
                    }
                    else
                    {
                        throw new KeyNotFoundException(key);
                    }
                }

                return dictionary_[caseInsensitiveKey];
            }

            set
            {
                string caseInsensitiveKey = key.Trim().ToLower();
                dictionary_[caseInsensitiveKey] = value;
            }
        }

        /// <summary>
        /// Returns the first item that matches the matching delegate
        /// </summary>
        /// <param name="object">The object to test against</param>
        /// <param name="isMatch">The matching delegate</param>
        /// <returns>The the first item that matches the matching delegate</returns>
        public T Find(Object @object, IsMatch isMatch)
        {
            foreach (T item in Values)
            {
                if (isMatch(item, @object))
                {
                    return item;
                }
            }

            return default(T);
        }

        /// <summary>
        /// Returns all the items that match the matching delegate
        /// </summary>
        /// <param name="object">The object to test against</param>
        /// <param name="isMatch">The matching delegate</param>
        /// <returns>All the items that match the matching delegate</returns>
        public List<T> FindAll(Object @object, IsMatch isMatch)
        {
            List<T> foundList = new List<T>();
            foreach (T item in Values)
            {
                if (isMatch(item, @object))
                {
                    foundList.Add(item);
                }
            }

            return foundList;
        }

        public void Remove(string key)
        {
            string caseInsensitiveKey = key.Trim().ToLower();
            if (!dictionary_.ContainsKey(caseInsensitiveKey))
            {
                if (itemNotFound_ != null)
                {
                    itemNotFound_(this, new ItemNotFoundEventArgs(key));
                }
                else
                {
                    throw new KeyNotFoundException(key);
                }
            }

            if (beforeRemove_ != null)
            {
                beforeRemove_(this, new BeforeRemoveEventArgs(caseInsensitiveKey, this[caseInsensitiveKey]));
            }

            dictionary_.Remove(caseInsensitiveKey);
        }

        #endregion

        #region EventHandlers

        public delegate void RepeatedItemHandler(Object sender, RepeatedItemEventArgs args);
        public delegate void ItemNotFoundHandler(Object sender, ItemNotFoundEventArgs args);
        public delegate void AfterInsertHandler(Object sender, AfterInsertEventArgs args);
        public delegate void BeforeRemoveHandler(Object sender, BeforeRemoveEventArgs args);

        private event RepeatedItemHandler repeatedItem_;
        private event ItemNotFoundHandler itemNotFound_;
        private event AfterInsertHandler afterInsert_;
        private event BeforeRemoveHandler beforeRemove_;

        public RepeatedItemHandler RepeatedItem
        {
            get { return repeatedItem_; }
            set { repeatedItem_ = value; }
        }

        public ItemNotFoundHandler ItemNotFound
        {
            get { return itemNotFound_; }
            set { itemNotFound_ = value; }
        }

        public AfterInsertHandler AfterInsert
        {
            get { return afterInsert_; }
            set { afterInsert_ = value; }
        }

        public BeforeRemoveHandler BeforeRemove
        {
            get { return beforeRemove_; }
            set { beforeRemove_ = value; }
        }

        #endregion

        #region IDictionary Members

        void IDictionary.Add(Object key, Object value)
        {
            this.Add((string)key, (T)value);
        }

        void IDictionary.Clear()
        {
            dictionary_.Clear();
        }

        bool IDictionary.Contains(Object key)
        {
            return this.ContainsKey((string)key);
        }

        IDictionaryEnumerator IDictionary.GetEnumerator()
        {
            return dictionary_.GetEnumerator();
        }

        bool IDictionary.IsFixedSize
        {
            get { return false; }
        }

        bool IDictionary.IsReadOnly
        {
            get { return false; }
        }

        ICollection IDictionary.Keys
        {
            get { return dictionary_.Keys; }
        }

        void IDictionary.Remove(Object key)
        {
            this.Remove((string)key);
        }

        ICollection IDictionary.Values
        {
            get { return dictionary_.Values; }
        }

        Object IDictionary.this[Object key]
        {
            get
            {
                return this[(string)key];
            }
            set
            {
                this[(string)key] = (T)value;
            }
        }

        #endregion

        #region ICollection Members

        void ICollection.CopyTo(Array array, int index)
        {
            int i = 0;
            foreach (string key in dictionary_.Keys)
            {
                if (i >= index)
                {
                    array.SetValue(this[key], i);
                }
                ++i;
            }
        }

        int ICollection.Count
        {
            get 
            { 
                return dictionary_.Count; 
            }
        }

        bool ICollection.IsSynchronized
        {
            get 
            {
                return ((ICollection)dictionary_).IsSynchronized; 
            }
        }

        Object ICollection.SyncRoot
        {
            get 
            {
                return ((ICollection)dictionary_).SyncRoot; 
            }
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return dictionary_.GetEnumerator();
        }

        #endregion

        #region IXmlSerializableObject Members

        public XmlObjectSerializer GetSerializer()
        {
            return new XmlDictionarySerializer(this);
        }

        public XmlObjectDeserializer GetDeserializer()
        {
            return new StringKeyDictionaryDeserializer(this);
        }

        #endregion

        private Dictionary<string, T> dictionary_;
    }
}
